8.2.2 定制的日志记录器
要想创建一个定制的日志记录器,需要创建一个 Logger
类型值。可以给每个日志记录器配置一个单独的目的地,并独立设置其前缀和标志。让我们来看一个示例程序,这个示例程序展示了如何创建不同的 Logger
类型的指针变量来支持不同的日志等级,如代码清单8-11所示。
代码清单8-11 listing11.go
01 // 这个示例程序展示如何创建定制的日志记录器
02 package main
03
04 import (
05 "io"
06 "io/ioutil"
07 "log"
08 "os"
09 )
10
11 var (
12 Trace *log.Logger // 记录所有日志
13 Info *log.Logger // 重要的信息
14 Warning *log.Logger // 需要注意的信息
15 Error *log.Logger // 非常严重的问题
16 )
17
18 func init() {
19 file, err := os.OpenFile("errors.txt",
20 os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
21 if err != nil {
22 log.Fatalln("Failed to open error log file:", err)
23 }
24
25 Trace = log.New(ioutil.Discard,
26 "TRACE: ",
27 log.Ldate|log.Ltime|log.Lshortfile)
28
29 Info = log.New(os.Stdout,
30 "INFO: ",
31 log.Ldate|log.Ltime|log.Lshortfile)
32
33 Warning = log.New(os.Stdout,
34 "WARNING: ",
35 log.Ldate|log.Ltime|log.Lshortfile)
36
37 Error = log.New(io.MultiWriter(file, os.Stderr),
38 "ERROR: ",
39 log.Ldate|log.Ltime|log.Lshortfile)
40 }
41
42 func main() {
43 Trace.Println("I have something standard to say")
44 Info.Println("Special Information")
45 Warning.Println("There is something you need to know about")
46 Error.Println("Something has failed")
47 }
代码清单8-11展示了一段完整的程序,这段程序创建了4种不同的 Logger
类型的指针变量,分别命名为 Trace
、 Info
、 Warning
和 Error
。每个变量使用不同的配置,用来表示不同的重要程度。让我们来分析一下这段代码是如何工作的。
在第11行到第16行,我们为4个日志等级声明了4个 Logger
类型的指针变量,如代码清单8-12所示。
代码清单8-12 listing11.go:第11行到第16行
11 var (
12 Trace *log.Logger // 记录所有日志
13 Info *log.Logger // 重要的信息
14 Warning *log.Logger // 需要注意的信息
15 Error *log.Logger // 非常严重的问题
16 )
在代码清单8-12中可以看到对 Logger
类型的指针变量的声明。我们使用的变量名很简短,但是含义明确。接下来,让我们看一下 init()
函数的代码是如何创建每个 Logger
类型的值并将其地址赋给每个变量的,如代码清单8-13所示。
代码清单8-13 listing11.go:第25行到第39行
25 Trace = log.New(ioutil.Discard,
26 "TRACE: ",
27 log.Ldate|log.Ltime|log.Lshortfile)
28
29 Info = log.New(os.Stdout,
30 "INFO: ",
31 log.Ldate|log.Ltime|log.Lshortfile)
32
33 Warning = log.New(os.Stdout,
34 "WARNING: ",
35 log.Ldate|log.Ltime|log.Lshortfile)
36
37 Error = log.New(io.MultiWriter(file, os.Stderr),
38 "ERROR: ",
39 log.Ldate|log.Ltime|log.Lshortfile)
为了创建每个日志记录器,我们使用了 log
包的 New
函数,它创建并正确初始化一个 Logger
类型的值。函数 New
会返回新创建的值的地址。在 New
函数创建对应值的时候,我们需要给它传入一些参数,如代码清单8-14所示。
代码清单8-14 golang.org/src/log/log.go
// New创建一个新的Logger。out参数设置日志数据将被写入的目的地
// 参数prefix会在生成的每行日志的最开始出现
// 参数flag定义日志记录包含哪些属性
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
代码清单8-14展示了来自 log
包的源代码里的 New
函数的声明。第一个参数 out
指定了日志要写到的目的地。这个参数传入的值必须实现了 io.Writer
接口。第二个参数 prefix
是之前看到的前缀,而日志的标志则是最后一个参数。
在这个程序里, Trace
日志记录器使用了 ioutil
包里的 Discard
变量作为写到的目的地,如代码清单8-15所示。
代码清单8-15 listing11.go:第25行到第27行
25 Trace = log.New(ioutil.Discard,
26 "TRACE: ",
27 log.Ldate|log.Ltime|log.Lshortfile)
变量 Discard
有一些有意思的属性,如代码清单8-16所示。
代码清单8-16 golang.org/src/io/ioutil/ioutil.go
// devNull是一个用int作为基础类型的类型
type devNull int
// Discard是一个io.Writer,所有的Write调用都不会有动作,但是会成功返回
var Discard io.Writer = devNull(0)
// io.Writer接口的实现
func (devNull) Write(p []byte) (int, error) {
return len(p), nil
}
代码清单8-16展示了 Discard
变量的声明以及相关的实现。 Discard
变量的类型被声明为 io.Writer
接口类型,并被给定了一个 devNull
类型的值0。基于 devNull
类型实现的 Write
方法,会忽略所有写入这一变量的数据。当某个等级的日志不重要时,使用 Discard
变量可以禁用这个等级的日志。
日志记录器 Info
和 Warning
都使用 stdout
作为日志输出,如代码清单8-17所示。
代码清单8-17 listing11.go:第29行到第35行
29 Info = log.New(os.Stdout,
30 "INFO: ",
31 log.Ldate|log.Ltime|log.Lshortfile)
32
33 Warning = log.New(os.Stdout,
34 "WARNING: ",
35 log.Ldate|log.Ltime|log.Lshortfile)
变量 Stdout
的声明也有一些有意思的地方,如代码清单8-18所示。
代码清单8-18 golang.org/src/os/file.go
// Stdin、Stdout和Stderr是已经打开的文件,分别指向标准输入、标准输出和
// 标准错误的文件描述符
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
os/file_unix.go
// NewFile用给出的文件描述符和名字返回一个新File
func NewFile(fd uintptr, name string) *File {
在代码清单8-18中可以看到3个变量的声明,分别表示所有操作系统里都有的3个标准输入/输出,即 Stdin
、 Stdout
和 Stderr
。这3个变量都被声明为 File
类型的指针,这个类型实现了 io.Writer
接口。有了这个知识,我们来看一下最后的日志记录器 Error
,如代码清单8-19所示。
代码清单8-19 listing11.go:第37行到第39行
37 Error = log.New(io.MultiWriter(file, os.Stderr),
38 "ERROR: ",
39 log.Ldate|log.Ltime|log.Lshortfile)
在代码清单8-19中可以看到 New
函数的第一个参数来自一个特殊的函数。这个特殊的函数就是 io
包里的 MultiWriter
函数,如代码清单8-20所示。
代码清单8-20 包 io
里的 MultiWriter
函数的声明
io.MultiWriter(file, os.Stderr)
代码清单8-20单独展示了 MultiWriter
函数的调用。这个函数调用会返回一个 io.Writer
接口类型值,这个值包含之前打开的文件 file
,以及 stderr
。 MultiWriter
函数是一个变参函数,可以接受任意个实现了 io.Writer
接口的值。这个函数会返回一个 io.Writer
值,这个值会把所有传入的 io.Writer
的值绑在一起。当对这个返回值进行写入时,会向所有绑在一起的 io.Writer
值做写入。这让类似 log.New
这样的函数可以同时向多个 Writer
做输出。现在,当我们使用 Error
记录器记录日志时,输出会同时写到文件和 stderr
。
现在知道了该如何创建定制的记录器了,让我们看一下如何使用这些记录器来写日志消息,如代码清单8-21所示。
代码清单8-21 listing11.go:第42行到第47行
42 func main() {
43 Trace.Println("I have something standard to say")
44 Info.Println("Special Information")
45 Warning.Println("There is something you need to know about")
46 Error.Println("Something has failed")
47 }
代码清单8-21展示了代码清单8-11中的 main()
函数。在第43行到第46行,我们用自己创建的每个记录器写一条消息。每个记录器变量都包含一组方法,这组方法与 log
包里实现的那组函数完全一致,如代码清单8-22所示。
代码清单8-22展示了为 Logger
类型实现的所有方法。
代码清单8-22 不同的日志方法的声明
func (l *Logger) Fatal(v ...interface{})
func (l *Logger) Fatalf(format string, v ...interface{})
func (l *Logger) Fatalln(v ...interface{})
func (l *Logger) Flags() int
func (l *Logger) Output(calldepth int, s string) error
func (l *Logger) Panic(v ...interface{})
func (l *Logger) Panicf(format string, v ...interface{})
func (l *Logger) Panicln(v ...interface{})
func (l *Logger) Prefix() string
func (l *Logger) Print(v ...interface{})
func (l *Logger) Printf(format string, v ...interface{})
func (l *Logger) Println(v ...interface{})
func (l *Logger) SetFlags(flag int)
func (l *Logger) SetPrefix(prefix string)